In this vignette we’ll visualize the spatially-resolved, single cell RNA-seq profiles produced by the Xenium platform. The vignette demonstrates how to load the per-transcript location data, cell x gene matrix, cell segmentation, and cell centroid information available in the Xenium outputs. The resulting Seurat object will contain the gene expression profile of each cell, the centroid and boundary of each cell, and the location of each individual detected transcript. The per-cell gene expression profiles are similar to standard single-cell RNA-seq and can be analyzed using the same tools.
This vignette requires the latest version of Seurat on Github. First, you must install in-development versions of Seurat and SeuratObject that have Xenium support. It is recommended to update your package version at this step, as the required versions may not be fully captured in the dependencies yet.
library(remotes)
# remotes::install_github('satijalab/seurat')
library(Seurat)
library(SeuratObject)
This vignette is based on the Tiny subset dataset from 10x Genomics provided in the Fresh Frozen Mouse Brain for Xenium Explorer Demo. These analysis steps are also compatible with the larger Full coronal section, but will take longer to execute.
First we read in the dataset and create a Seurat object. Provide the path to the data folder for a Xenium run as the input path. The RNA data is stored in the Xenium assay of the Seurat object. This step should take about a minute.
path <- "Xenium_V1_FF_Mouse_Brain_Coronal_Subset_CTX+HP/"
# Load the Xenium data
xenium.obj <- LoadXenium(path, fov = "fov")
# remove cells with 0 counts -- these cause problems for SCTransform
xenium.obj <- subset(xenium.obj, subset = nCount_Xenium > 0)
Spatial information is loaded into slots of the Seurat object, labelled by the name of “field of view” (FOV) being loaded. Initially all the data is loaded into the FOV named fov, by convention in this vignette. Later, we will make a cropped FOV that zooms into a region of interest. Here is a summary of the spatial information stored in the fov slot of the resulting Seurat object:
# Get the center position of each centroid. There is one row per cell in this dataframe.
head(GetTissueCoordinates(xenium.obj[["fov"]][["centroids"]]))
| x | y | cell |
|---|---|---|
| 1898.815 | 2540.963 | 1 |
| 1895.305 | 2532.627 | 2 |
| 2368.073 | 2534.409 | 3 |
| 1903.726 | 2560.010 | 4 |
| 1917.481 | 2543.132 | 5 |
| 1926.540 | 2560.044 | 6 |
# Get the coordinates for each segmentation vertex. Each cell will have a variable number of
# vertices describing its shape.
head(GetTissueCoordinates(xenium.obj[["fov"]][["segmentation"]]))
| x | y | cell |
|---|---|---|
| 1901.875 | 2526.413 | 1 |
| 1901.450 | 2537.038 | 1 |
| 1900.175 | 2539.375 | 1 |
| 1896.562 | 2539.800 | 1 |
| 1885.938 | 2537.887 | 1 |
| 1882.963 | 2542.775 | 1 |
# Fetch molecules positions for Gad1
head(FetchData(xenium.obj[["fov"]][["molecules"]], vars = "Gad1"))
| x | y | molecule |
|---|---|---|
| 4854.597 | 756.7019 | Gad1 |
| 4859.171 | 658.6622 | Gad1 |
| 4867.069 | 864.6551 | Gad1 |
| 4868.991 | 777.4815 | Gad1 |
| 4873.433 | 885.4102 | Gad1 |
| 4873.344 | 795.4069 | Gad1 |
Standard QC plots provided by Seurat are available via the Xenium assay. Here are violin plots of genes per cell (nFeature_Xenium) and transcript counts per cell (nCount_Xenium)
VlnPlot(xenium.obj, features = c("nFeature_Xenium", "nCount_Xenium"), ncol = 2, pt.size = 0)
Check that the cell and molecule information is loaded correctly by plotting the transcript locations of a few marker genes over the cell centroids. ImageDimPlot() can display cell positions and overlay the location of individual molecules via the molecules argument. The nmols argument is used to downsample the number of transcript locations displayed to control overplotting.
ImageDimPlot(xenium.obj, fov = "fov", molecules = c("Cux2", "Foxp2"), nmols = 20000, mols.cols = c("blue",
"green"), axes = TRUE)
Here’s a similar plot, including the pan-inhibitory neuron marker Gad1, inhibitory neuron sub-type markers Pvalb, and Sst, and astrocyte marker Gfap.
ImageDimPlot(xenium.obj, fov = "fov", molecules = c("Gad1", "Sst", "Pvalb", "Gfap"), nmols = 20000)
Here we visualize the expression level of some key layer marker genes at the per-cell level using ImageFeaturePlot() which is analogous to the FeaturePlot() function for visualizing expression on a 2D embedding. We manually adjust the max.cutoff for each gene to roughly the 90th percentile of it’s count distribution to improve contrast.
ImageFeaturePlot(xenium.obj, features = c("Cux2", "Rorb", "Bcl11b", "Foxp2"), max.cutoff = c(25,
35, 12, 10), size = 0.75, cols = c("white", "red"))
We can zoom in on a chosen area with the Crop() function. Once zoomed-in, we can visualize cell segmentation boundaries along with individual molecules.
cropped.coords <- Crop(xenium.obj[["fov"]], x = c(1200, 2900), y = c(3750, 4550), coords = "plot")
xenium.obj[["zoom"]] <- cropped.coords
# visualize cropped area with cell segmentations & selected molecules
DefaultBoundary(xenium.obj[["zoom"]]) <- "segmentation"
ImageDimPlot(xenium.obj, fov = "zoom", axes = TRUE, border.color = "white", border.size = 0.1, cols = "polychrome",
coord.fixed = FALSE, molecules = c("Gad1", "Sst", "Npy2r", "Pvalb", "Nrn1"), nmols = 10000)
We start by performing a standard unsupervised clustering analysis, essentially first treating the dataset as a scRNA-seq experiment. We use SCTransform-based normalization followed by standard dimensionality reduction and clustering. This step takes about 5 minutes from start to finish.
xenium.obj <- SCTransform(xenium.obj, assay = "Xenium")
xenium.obj <- RunPCA(xenium.obj, npcs = 30, features = rownames(xenium.obj))
xenium.obj <- RunUMAP(xenium.obj, dims = 1:30)
xenium.obj <- FindNeighbors(xenium.obj, reduction = "pca", dims = 1:30)
xenium.obj <- FindClusters(xenium.obj, resolution = 0.3)
We can then visualize the results of the clustering by coloring each cell according to its cluster either in UMAP space with DimPlot() or overlaid on the image with ImageDimPlot().
UMAPPlot(xenium.obj)
We can visualize the expression level of the markers we looked at earlier on the UMAP coordinates.
FeaturePlot(xenium.obj, features = c("Cux2", "Bcl11b", "Foxp2", "Gad1", "Sst", "Gfap"))
We can now use ImageDimPlot() to color the cell positions colored by the cluster labels determined in the previous step.
ImageDimPlot(xenium.obj, cols = "polychrome", axes = TRUE, size = 0.75)
This vignette and the analysis methods for in-situ data are a work in progress. Please contact patrick@10xgenomics.com if you have questions or requests for additional examples. If you have any issues with the code working, please report the results of sessionInfo().
sessionInfo()
## R version 4.0.3 (2020-10-10)
## Platform: x86_64-conda-linux-gnu (64-bit)
## Running under: Amazon Linux 2
##
## Matrix products: default
## BLAS/LAPACK: /mnt/opt/R/R-4.0.3-conda/env/lib/libopenblasp-r0.3.12.so
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] sp_1.4-4 SeuratObject_4.1.1 Seurat_4.1.0.9007 remotes_2.2.0
##
## loaded via a namespace (and not attached):
## [1] Rtsne_0.15 colorspace_2.0-3 deldir_1.0-6
## [4] ggridges_0.5.2 rstudioapi_0.11 spatstat.data_3.0-0
## [7] farver_2.0.3 leiden_0.3.3 listenv_0.8.0
## [10] bit64_4.0.5 ggrepel_0.8.2 fansi_1.0.3
## [13] R.methodsS3_1.8.1 codetools_0.2-16 splines_4.0.3
## [16] cachem_1.0.6 knitr_1.40 polyclip_1.10-4
## [19] jsonlite_1.8.4 ica_1.0-2 cluster_2.1.0
## [22] R.oo_1.24.0 png_0.1-7 rgeos_0.5-9
## [25] uwot_0.1.14 shiny_1.5.0 sctransform_0.3.4
## [28] spatstat.sparse_3.0-0 compiler_4.0.3 httr_1.4.2
## [31] assertthat_0.2.1 Matrix_1.5-1 fastmap_1.1.0
## [34] lazyeval_0.2.2 cli_3.4.1 later_1.3.0
## [37] formatR_1.7 htmltools_0.5.3 tools_4.0.3
## [40] igraph_1.2.6 gtable_0.3.1 glue_1.6.2
## [43] RANN_2.6.1 reshape2_1.4.4 dplyr_1.0.10
## [46] Rcpp_1.0.9 scattermore_0.8 jquerylib_0.1.4
## [49] vctrs_0.5.0 nlme_3.1-150 progressr_0.11.0
## [52] lmtest_0.9-38 spatstat.random_3.0-1 xfun_0.34
## [55] stringr_1.4.0 globals_0.16.2 mime_0.9
## [58] miniUI_0.1.1.1 lifecycle_1.0.3 irlba_2.3.3
## [61] goftest_1.2-2 future_1.19.1 MASS_7.3-53
## [64] zoo_1.8-8 scales_1.2.1 spatstat.core_2.4-4
## [67] promises_1.1.1 spatstat.utils_3.0-1 parallel_4.0.3
## [70] RColorBrewer_1.1-2 yaml_2.3.6 reticulate_1.18
## [73] pbapply_1.4-3 gridExtra_2.3 ggplot2_3.3.6
## [76] sass_0.4.2 rpart_4.1-15 stringi_1.7.8
## [79] highr_0.8 rlang_1.0.6 pkgconfig_2.0.3
## [82] matrixStats_0.57.0 evaluate_0.17 lattice_0.20-41
## [85] tensor_1.5 ROCR_1.0-11 purrr_0.3.5
## [88] labeling_0.4.2 patchwork_1.0.1.9000 htmlwidgets_1.5.2
## [91] bit_4.0.4 cowplot_1.1.0 tidyselect_1.2.0
## [94] RcppAnnoy_0.0.19 plyr_1.8.8 magrittr_2.0.3
## [97] R6_2.5.1 generics_0.1.3 DBI_1.1.0
## [100] mgcv_1.8-33 pillar_1.8.1 fitdistrplus_1.1-1
## [103] abind_1.4-5 survival_3.2-7 tibble_3.1.8
## [106] future.apply_1.6.0 crayon_1.3.4 KernSmooth_2.23-17
## [109] utf8_1.2.2 spatstat.geom_3.0-3 plotly_4.9.2.1
## [112] rmarkdown_2.17 grid_4.0.3 data.table_1.14.2
## [115] blob_1.2.1 digest_0.6.31 xtable_1.8-4
## [118] tidyr_1.1.2 httpuv_1.5.4 R.utils_2.10.1
## [121] munsell_0.5.0 viridisLite_0.3.0 bslib_0.4.0